package code

import (
	"bytes"
	"encoding/binary"
	"fmt"
)

const (
	OpConstant Opcode = iota // OpConstant = 1,用于操作常量
	OpAdd
	OpPop
	OpSub
	OpMul
	OpDiv
	OpTrue
	OpFalse
	OpEqual
	OpNotEqual
	OpGreaterThan
	OpMinus
	OpBang
	OpJumpNotTruthy
	OpJump
	OpNull
	OpGetGlobal
	OpSetGlobal
	OpArray
	OpHash
	OpIndex
	OpCall
	OpReturnValue
	OpReturn
	OpGetLocal
	OpSetLocal
	OpGetBuiltin
	OpClosure
	OpGetFree
	OpCurrentClosure
)

var definitions = map[Opcode]*Definition{
	OpConstant:      {"OpConstant", []int{2}}, // 此操作码有操作数
	OpAdd:           {"OpAdd", []int{}},
	OpPop:           {"OpPop", []int{}}, // 将栈顶元素弹出，在表达式之后执行
	OpSub:           {"OpSub", []int{}},
	OpMul:           {"OpMul", []int{}},
	OpDiv:           {"OpDiv", []int{}},
	OpTrue:          {"OpTrue", []int{}},
	OpFalse:         {"OpFalse", []int{}},
	OpEqual:         {"OpEqual", []int{}},
	OpNotEqual:      {"OpNotEqual", []int{}},
	OpGreaterThan:   {"OpGreaterThan", []int{}},
	OpMinus:         {"OpMinus", []int{}},
	OpBang:          {"OpBang", []int{}},           // 以上操作码都没有操作数，操作的是栈顶元素
	OpJumpNotTruthy: {"OpJumpNotTruthy", []int{2}}, // 条件语句跳转指令，操作数为跳转指令的偏移量
	OpJump:          {"OpJump", []int{2}},          // 跳转指令，操作数为跳转指令的偏移量
	OpNull:          {"OpNull", []int{}},           // 栈中压入 Null
	OpGetGlobal:     {"OpGetGlobal", []int{2}},     // 操作数是常量池索引
	OpSetGlobal:     {"OpSetGlobal", []int{2}},
	OpArray:         {"OpArray", []int{2}}, // 操作数为数组长度
	OpHash:          {"OpHash", []int{2}},  // 操作数为键和值数量
	OpIndex:         {"OpIndex", []int{}},
	OpCall:          {"OpCall", []int{1}},       // 将要调用的函数压栈，然后将函数的参数压栈，随后发出 OpCall 指令，让虚拟机执行栈顶的函数，该指令操作数为参数数量
	OpReturnValue:   {"OpReturnValue", []int{}}, // 用于让虚拟机从函数返回栈顶的值
	OpReturn:        {"OpReturn", []int{}},      // 回到调用函数之前的逻辑，函数无返回值
	OpGetLocal:      {"OpGetLocal", []int{1}},   // 局部绑定操作码的操作数为 1 字节，最多 256 个局部绑定
	OpSetLocal:      {"OpSetLocal", []int{1}},
	OpGetBuiltin:    {"OpGetBuiltin", []int{1}}, // 目前最多可以定义 256 个内置函数
	OpClosure:       {"OpClosure", []int{2, 1}}, // 第一个操作数是常量索引,指定在常量池中哪个位置找到要转换为闭包的 *object.CompiledFunction
	// 第二个操作数用于指定栈中有多少个自由变量需要转移到即将创建的闭包中
	OpGetFree:        {"OpGetFree", []int{1}}, // 用于检索 Free 字段中的值，并将它压栈
	OpCurrentClosure: {"OpCurrentClosure", []int{}},
}

type Instructions []byte

// 输出指令
func (ins Instructions) String() string {
	var out bytes.Buffer

	i := 0
	for i < len(ins) { // 迭代逐条输出指令内容
		def, err := Lookup(ins[i])
		if err != nil {
			fmt.Fprintf(&out, "ERROR: %s\n", err)
			continue
		}

		operands, read := ReadOperands(def, ins[i+1:])

		fmt.Fprintf(&out, "%04d %s\n", i, ins.fmtInstruction(def, operands))

		i += 1 + read
	}

	return out.String()
}

// 格式化指令输出
func (ins Instructions) fmtInstruction(def *Definition, operands []int) string {
	operandCount := len(def.OperandWidths)

	if len(operands) != operandCount {
		return fmt.Sprintf("ERROR: operand len %d does not match defined %d\n", len(operands), operandCount)
	}

	switch operandCount {
	case 0:
		return def.Name
	case 1:
		return fmt.Sprintf("%s %d", def.Name, operands[0])
	case 2:
		return fmt.Sprintf("%s %d %d", def.Name, operands[0], operands[1])
	}

	return fmt.Sprintf("ERROR: unhandled operandCount for %s\n", def.Name)
}

type Opcode byte

type Definition struct {
	Name          string
	OperandWidths []int
}

func Lookup(op byte) (*Definition, error) {
	def, ok := definitions[Opcode(op)]
	if !ok {
		return nil, fmt.Errorf("opcode %d undefined", op)
	}

	return def, nil
}

// Make 根据操作码和操作数生成字节码
func Make(op Opcode, operands ...int) []byte {
	def, ok := definitions[op] // 取操作码定义信息
	if !ok {
		return []byte{}
	}

	instructionLen := 1 // 指令起始长度
	for _, w := range def.OperandWidths {
		instructionLen += w // 指令长度 + 操作数长度（字节数，一个操作数占 w 字节 ？？）
	}

	instruction := make([]byte, instructionLen) // 根据指令长度初始化指令字节
	instruction[0] = byte(op)                   // 指令第一个字节为操作码

	offset := 1 // 指令字节内偏移量
	for i, o := range operands {
		width := def.OperandWidths[i] // 操作数宽度（所占字节数）
		switch width {
		case 2: // 操作数占 2 字节（测试为 65534）
			binary.BigEndian.PutUint16(instruction[offset:], uint16(o))
		case 1:
			instruction[offset] = byte(o)
		}

		offset += width // 更新偏移量
	}

	return instruction
}

func ReadOperands(def *Definition, ins Instructions) ([]int, int) {
	operands := make([]int, len(def.OperandWidths))
	offset := 0

	for i, width := range def.OperandWidths {
		switch width {
		case 2:
			operands[i] = int(ReadUint16(ins[offset:]))
		case 1:
			operands[i] = int(ReadUint8(ins[offset:]))
		}

		offset += width
	}

	return operands, offset
}

func ReadUint16(ins Instructions) uint16 {
	return binary.BigEndian.Uint16(ins)
}

func ReadUint8(ins Instructions) uint8 { return ins[0] }
